class Point
  attr_reader:x
  attr_reader:y
  def initialize(x, y)
    @x = x;
    @y = y;
  end

  def ==(rhs)
    return @x == rhs.x && @y == rhs.y;
  end

  def out()
    print "x = "
    print x
    print ";y="
    p y
  end
end


class Direct
  attr_reader:index
  def initialize(index,  x, y)
    @index = index
    @x_offset = x
    @y_offset = y
  end

  def forward(point)
    return Point.new(point.x + @x_offset, point.y + @y_offset)
  end

  def backward(point)
    return Point.new(point.x - @x_offset, point.y - @y_offset)
  end

  def out()
    print "direct ="
    puts index
  end
end

$north = Direct.new(0,0,1);
$east = Direct.new(1,1,0);
$south = Direct.new(2,0,-1);
$west = Direct.new(3,-1,0);
$directs = [
  $north, $east, $south, $west
]

$turnLeft = lambda{|pos| return Position.new(pos.point, $directs[(pos.direct.index + 3) %4]);}

$turnRight =lambda{|pos| return Position.new(pos.point, $directs[(pos.direct.index + 1) %4])}

$forward = lambda{|pos|   return  Position.new(pos.direct.forward(pos.point),pos.direct)
}

$backward = lambda{|pos| return Position.new(pos.direct.backward(pos.point),pos.direct)}

def trail(cmds)
  lambda{|pos|
  cmds.each do |cmd|
    pos = cmd.call(pos)
  end
  return pos
  }
end

def repeat(n, cmd)
  cmds = []
  (1 .. n) .each do
    cmds << cmd
  end
  trail(cmds)
end

def arrand()
  repeat(2, $turnLeft);
end

class Position
  attr_reader:point
  attr_reader:direct
  def initialize(point, d)
    @point=point;
    @direct=d;
  end

  def ==(rhs)
    return rhs.point == @point &&
           rhs.direct == @direct
  end

  def out()
    point.out()
    direct.out()
  end
end

class Robot
  def initialize()
    @position = Position.new(Point.new(0,0), $north)
  end
  
  def onCmd(cmd)
    @position = cmd.call(@position)
    return @position; 
  end

end

require "test/unit"

class RobotTest < Test::Unit::TestCase
  def test_turn_left
    assert((Robot.new.onCmd($turnLeft) == Position.new(Point.new(0,0),$west)))
  end

  def test_turn_right
    assert((Robot.new.onCmd($turnRight) == Position.new(Point.new(0,0),$east)))
  end

  def test_move_forward
    assert((Robot.new.onCmd($forward) == Position.new(Point.new(0,1),$north)))
  end

  def test_move_backward
    assert((Robot.new.onCmd($backward) == Position.new(Point.new(0,-1),$north)))
  end

  def test_turn_arround
    assert((Robot.new.onCmd(arrand()) == Position.new(Point.new(0,0),$south)))
  end

  def test_forward_n
    assert((Robot.new.onCmd(repeat(2,$forward))) ==  Position.new(Point.new(0,2), $north))
  end
  
  def test_turn_left_n
    assert((Robot.new.onCmd(repeat(2,$turnLeft))) ==  Position.new(Point.new(0,0), $south))
  end

  def test_trail_cmd
    assert((Robot.new.onCmd(trail([$turnRight, $forward]))) == Position.new(Point.new(1,0),$east))
  end

  def test_trail_and_repeat_cmd
    assert((Robot.new.onCmd(trail([repeat(2,$turnRight), $forward]))) == Position.new(Point.new(0,-1),$south))
  end

  def test_repeat_trail_and_repeat_cmd
    assert((Robot.new.onCmd(repeat(2,trail([repeat(2,$turnRight), $forward])))) == Position.new(Point.new(0,0),$north))
  end
end

